/*
 * pppdump - print out the contents of a record file generated by
 * pppd in readable form.
 *
 * Copyright (C) 1999  Paul Mackerras.  All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version
 *  2 of the License, or (at your option) any later version.
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <sys/types.h>
#ifdef PPP_DEFS_IN_NET
#include <net/ppp_defs.h>
#else
#include "ppp_defs.h"
#endif
#include "ppp-comp.h"

int hexmode;
int pppmode;
int reverse;
int decompress;
int mru = 1500;
int abs_times;
time_t start_time;
int start_time_tenths;
int tot_sent, tot_rcvd;

extern int optind;
extern char *optarg;

void dumplog();
void dumpppp();
void show_time();
void handle_ccp();

int
main(ac, av)
    int ac;
    char **av;
{
    int i;
    char *p;
    FILE *f;

    while ((i = getopt(ac, av, "hprdm:a")) != -1) {
	switch (i) {
	case 'h':
	    hexmode = 1;
	    break;
	case 'p':
	    pppmode = 1;
	    break;
	case 'r':
	    reverse = 1;
	    break;
	case 'd':
	    decompress = 1;
	    break;
	case 'm':
	    mru = atoi(optarg);
	    break;
	case 'a':
	    abs_times = 1;
	    break;
	default:
	    fprintf(stderr, "Usage: %s [-h | -p[d]] [-r] [-m mru] [-a] [file ...]\n", av[0]);
	    exit(1);
	}
    }
    if (optind >= ac)
	dumplog(stdin);
    else {
	for (i = optind; i < ac; ++i) {
	    p = av[i];
	    if ((f = fopen(p, "r")) == NULL) {
		perror(p);
		exit(1);
	    }
	    if (pppmode)
		dumpppp(f);
	    else
		dumplog(f);
	    fclose(f);
	}
    }
    exit(0);
}

void
dumplog(f)
    FILE *f;
{
    int c, n, k, col;
    int nb, c2;
    unsigned char buf[16];

    while ((c = getc(f)) != EOF) {
	switch (c) {
	case RECMARK_STARTSEND:
	case RECMARK_STARTRECV:
	    if (reverse)
		c = c==RECMARK_STARTSEND ? RECMARK_STARTRECV :
		    RECMARK_STARTSEND;
	    printf("%s %c", c==RECMARK_STARTSEND? "sent": "rcvd",
		hexmode? ' ': '"');
	    col = 6;
	    n = getc(f);
	    n = (n << 8) + getc(f);
	    *(c==1? &tot_sent: &tot_rcvd) += n;
	    nb = 0;
	    for (; n > 0; --n) {
		c = getc(f);
		if (c == EOF) {
		    printf("\nEOF\n");
		    exit(0);
		}
		if (hexmode) {
		    if (nb >= 16) {
			printf("  ");
			for (k = 0; k < nb; ++k) {
			    c2 = buf[k];
			    putchar((' ' <= c2 && c2 <= '~')? c2: '.');
			}
			printf("\n      ");
			nb = 0;
		    }
		    buf[nb++] = c;
		    printf(" %.2x", c);
		} else {
		    k = (' ' <= c && c <= '~')? (c != '\\' && c != '"')? 1: 2: 3;
		    if ((col += k) >= 78) {
			printf("\n      ");
			col = 6 + k;
		    }
		    switch (k) {
		    case 1:
			putchar(c);
			break;
		    case 2:
			printf("\\%c", c);
			break;
		    case 3:
			printf("\\%.2x", c);
			break;
		    }
		}
	    }
	    if (hexmode) {
		for (k = nb; k < 16; ++k)
		    printf("   ");
		printf("  ");
		for (k = 0; k < nb; ++k) {
		    c2 = buf[k];
		    putchar((' ' <= c2 && c2 <= '~')? c2: '.');
		}
	    } else
		putchar('"');
	    printf("\n");
	    break;
	case RECMARK_ENDSEND:
	case RECMARK_ENDRECV:
	    if (reverse)
		c = c==RECMARK_ENDSEND ? RECMARK_ENDRECV : RECMARK_ENDSEND;
	    printf("end %s\n", c==RECMARK_ENDSEND? "send": "recv");
	    break;
	case RECMARK_TIMEDELTA32:
	case RECMARK_TIMEDELTA8:
	case RECMARK_TIMESTART:
	    show_time(f, c);
	    break;
	default:
	    printf("?%.2x\n");
	}
    }
}

/*
 * FCS lookup table as calculated by genfcstab.
 */
static u_short fcstab[256] = {
	0x0000,	0x1189,	0x2312,	0x329b,	0x4624,	0x57ad,	0x6536,	0x74bf,
	0x8c48,	0x9dc1,	0xaf5a,	0xbed3,	0xca6c,	0xdbe5,	0xe97e,	0xf8f7,
	0x1081,	0x0108,	0x3393,	0x221a,	0x56a5,	0x472c,	0x75b7,	0x643e,
	0x9cc9,	0x8d40,	0xbfdb,	0xae52,	0xdaed,	0xcb64,	0xf9ff,	0xe876,
	0x2102,	0x308b,	0x0210,	0x1399,	0x6726,	0x76af,	0x4434,	0x55bd,
	0xad4a,	0xbcc3,	0x8e58,	0x9fd1,	0xeb6e,	0xfae7,	0xc87c,	0xd9f5,
	0x3183,	0x200a,	0x1291,	0x0318,	0x77a7,	0x662e,	0x54b5,	0x453c,
	0xbdcb,	0xac42,	0x9ed9,	0x8f50,	0xfbef,	0xea66,	0xd8fd,	0xc974,
	0x4204,	0x538d,	0x6116,	0x709f,	0x0420,	0x15a9,	0x2732,	0x36bb,
	0xce4c,	0xdfc5,	0xed5e,	0xfcd7,	0x8868,	0x99e1,	0xab7a,	0xbaf3,
	0x5285,	0x430c,	0x7197,	0x601e,	0x14a1,	0x0528,	0x37b3,	0x263a,
	0xdecd,	0xcf44,	0xfddf,	0xec56,	0x98e9,	0x8960,	0xbbfb,	0xaa72,
	0x6306,	0x728f,	0x4014,	0x519d,	0x2522,	0x34ab,	0x0630,	0x17b9,
	0xef4e,	0xfec7,	0xcc5c,	0xddd5,	0xa96a,	0xb8e3,	0x8a78,	0x9bf1,
	0x7387,	0x620e,	0x5095,	0x411c,	0x35a3,	0x242a,	0x16b1,	0x0738,
	0xffcf,	0xee46,	0xdcdd,	0xcd54,	0xb9eb,	0xa862,	0x9af9,	0x8b70,
	0x8408,	0x9581,	0xa71a,	0xb693,	0xc22c,	0xd3a5,	0xe13e,	0xf0b7,
	0x0840,	0x19c9,	0x2b52,	0x3adb,	0x4e64,	0x5fed,	0x6d76,	0x7cff,
	0x9489,	0x8500,	0xb79b,	0xa612,	0xd2ad,	0xc324,	0xf1bf,	0xe036,
	0x18c1,	0x0948,	0x3bd3,	0x2a5a,	0x5ee5,	0x4f6c,	0x7df7,	0x6c7e,
	0xa50a,	0xb483,	0x8618,	0x9791,	0xe32e,	0xf2a7,	0xc03c,	0xd1b5,
	0x2942,	0x38cb,	0x0a50,	0x1bd9,	0x6f66,	0x7eef,	0x4c74,	0x5dfd,
	0xb58b,	0xa402,	0x9699,	0x8710,	0xf3af,	0xe226,	0xd0bd,	0xc134,
	0x39c3,	0x284a,	0x1ad1,	0x0b58,	0x7fe7,	0x6e6e,	0x5cf5,	0x4d7c,
	0xc60c,	0xd785,	0xe51e,	0xf497,	0x8028,	0x91a1,	0xa33a,	0xb2b3,
	0x4a44,	0x5bcd,	0x6956,	0x78df,	0x0c60,	0x1de9,	0x2f72,	0x3efb,
	0xd68d,	0xc704,	0xf59f,	0xe416,	0x90a9,	0x8120,	0xb3bb,	0xa232,
	0x5ac5,	0x4b4c,	0x79d7,	0x685e,	0x1ce1,	0x0d68,	0x3ff3,	0x2e7a,
	0xe70e,	0xf687,	0xc41c,	0xd595,	0xa12a,	0xb0a3,	0x8238,	0x93b1,
	0x6b46,	0x7acf,	0x4854,	0x59dd,	0x2d62,	0x3ceb,	0x0e70,	0x1ff9,
	0xf78f,	0xe606,	0xd49d,	0xc514,	0xb1ab,	0xa022,	0x92b9,	0x8330,
	0x7bc7,	0x6a4e,	0x58d5,	0x495c,	0x3de3,	0x2c6a,	0x1ef1,	0x0f78
};

struct pkt {
    int	cnt;
    int	esc;
    int	flags;
    struct compressor *comp;
    void *state;
    unsigned char buf[8192];
} spkt, rpkt;

/* Values for flags */
#define CCP_ISUP	1
#define CCP_ERROR	2
#define CCP_FATALERROR	4
#define CCP_ERR		(CCP_ERROR | CCP_FATALERROR)
#define CCP_DECOMP_RUN	8

unsigned char dbuf[8192];

void
dumpppp(f)
    FILE *f;
{
    int c, n, k;
    int nb, nl, dn, proto, rv;
    char *dir, *q;
    unsigned char *p, *r, *endp;
    unsigned char *d;
    unsigned short fcs;
    struct pkt *pkt;

    spkt.cnt = rpkt.cnt = 0;
    spkt.esc = rpkt.esc = 0;
    while ((c = getc(f)) != EOF) {
	switch (c) {
	case RECMARK_STARTSEND:
	case RECMARK_STARTRECV:
	    if (reverse)
		c = c==RECMARK_STARTSEND ? RECMARK_STARTRECV :
		    RECMARK_STARTSEND;
	    dir = c==RECMARK_STARTSEND? "sent": "rcvd";
	    pkt = c==RECMARK_STARTSEND? &spkt: &rpkt;
	    n = getc(f);
	    n = (n << 8) + getc(f);
	    *(c==1? &tot_sent: &tot_rcvd) += n;
	    for (; n > 0; --n) {
		c = getc(f);
		switch (c) {
		case EOF:
		    printf("\nEOF\n");
		    if (spkt.cnt > 0)
			printf("[%d bytes in incomplete send packet]\n",
			       spkt.cnt);
		    if (rpkt.cnt > 0)
			printf("[%d bytes in incomplete recv packet]\n",
			       rpkt.cnt);
		    exit(0);
		case '~':
		    if (pkt->cnt > 0) {
			q = dir;
			if (pkt->esc) {
			    printf("%s aborted packet:\n     ", dir);
			    q = "    ";
			}
			nb = pkt->cnt;
			p = pkt->buf;
			pkt->cnt = 0;
			pkt->esc = 0;
			if (nb <= 2) {
			    printf("%s short packet [%d bytes]:", q, nb);
			    for (k = 0; k < nb; ++k)
				printf(" %.2x", p[k]);
			    printf("\n");
			    break;
			}
			fcs = PPP_INITFCS;
			for (k = 0; k < nb; ++k)
			    fcs = PPP_FCS(fcs, p[k]);
			fcs &= 0xFFFF;
			nb -= 2;
			endp = p + nb;
			r = p;
			if (r[0] == 0xff && r[1] == 3)
			    r += 2;
			if ((r[0] & 1) == 0)
			    ++r;
			++r;
			if (endp - r > mru)
			    printf("     ERROR: length (%d) > MRU (%d)\n",
				   endp - r, mru);
			if (decompress && fcs == PPP_GOODFCS) {
			    /* See if this is a CCP or compressed packet */
			    d = dbuf;
			    r = p;
			    if (r[0] == 0xff && r[1] == 3) {
				*d++ = *r++;
				*d++ = *r++;
			    }
			    proto = r[0];
			    if ((proto & 1) == 0)
				proto = (proto << 8) + r[1];
			    if (proto == PPP_CCP) {
				handle_ccp(pkt, r + 2, endp - r - 2);
			    } else if (proto == PPP_COMP) {
				if ((pkt->flags & CCP_ISUP)
				    && (pkt->flags & CCP_DECOMP_RUN)
				    && pkt->state
				    && (pkt->flags & CCP_ERR) == 0) {
				    rv = pkt->comp->decompress(pkt->state, r,
							endp - r, d, &dn);
				    switch (rv) {
				    case DECOMP_OK:
					p = dbuf;
					nb = d + dn - p;
					if ((d[0] & 1) == 0)
					    --dn;
					--dn;
					if (dn > mru)
					    printf("     ERROR: decompressed length (%d) > MRU (%d)\n", dn, mru);
					break;
				    case DECOMP_ERROR:
					printf("     DECOMPRESSION ERROR\n");
					pkt->flags |= CCP_ERROR;
					break;
				    case DECOMP_FATALERROR:
					printf("     FATAL DECOMPRESSION ERROR\n");
					pkt->flags |= CCP_FATALERROR;
					break;
				    }
				}
			    } else if (pkt->state
				       && (pkt->flags & CCP_DECOMP_RUN)) {
				pkt->comp->incomp(pkt->state, r, endp - r);
			    }
			}
			do {
			    nl = nb < 16? nb: 16;
			    printf("%s ", q);
			    for (k = 0; k < nl; ++k)
				printf(" %.2x", p[k]);
			    for (; k < 16; ++k)
				printf("   ");
			    printf("  ");
			    for (k = 0; k < nl; ++k) {
				c = p[k];
				putchar((' ' <= c && c <= '~')? c: '.');
			    }
			    printf("\n");
			    q = "    ";
			    p += nl;
			    nb -= nl;
			} while (nb > 0);
			if (fcs != PPP_GOODFCS)
			    printf("     BAD FCS: (residue = %x)\n", fcs);
		    }
		    break;
		case '}':
		    if (!pkt->esc) {
			pkt->esc = 1;
			break;
		    }
		    /* else fall through */
		default:
		    if (pkt->esc) {
			c ^= 0x20;
			pkt->esc = 0;
		    }
		    pkt->buf[pkt->cnt++] = c;
		    break;
		}
	    }
	    break;
	case RECMARK_ENDSEND:
	case RECMARK_ENDRECV:
	    if (reverse)
		c = c==RECMARK_ENDSEND ? RECMARK_ENDRECV : RECMARK_ENDSEND;
	    dir = c==RECMARK_ENDSEND ? "send": "recv";
	    pkt = c==RECMARK_ENDSEND ? &spkt: &rpkt;
	    printf("end %s", dir);
	    if (pkt->cnt > 0)
		printf("  [%d bytes in incomplete packet]", pkt->cnt);
	    printf("\n");
	    break;
	case RECMARK_TIMEDELTA32:
	case RECMARK_TIMEDELTA8:
	case RECMARK_TIMESTART:
	    show_time(f, c);
	    break;
	default:
	    printf("?%.2x\n");
	}
    }
}

extern struct compressor ppp_bsd_compress, ppp_deflate;

struct compressor *compressors[] = {
#if DO_BSD_COMPRESS
    &ppp_bsd_compress,
#endif
#if DO_DEFLATE
    &ppp_deflate,
#endif
    NULL
};

void
handle_ccp(cp, dp, len)
    struct pkt *cp;
    u_char *dp;
    int len;
{
    int clen;
    struct compressor **comp;

    if (len < CCP_HDRLEN)
	return;
    clen = CCP_LENGTH(dp);
    if (clen > len)
	return;

    switch (CCP_CODE(dp)) {
    case CCP_CONFACK:
	cp->flags &= ~(CCP_DECOMP_RUN | CCP_ISUP);
	if (clen < CCP_HDRLEN + CCP_OPT_MINLEN
	    || clen < CCP_HDRLEN + CCP_OPT_LENGTH(dp + CCP_HDRLEN))
	    break;
	dp += CCP_HDRLEN;
	clen -= CCP_HDRLEN;
	for (comp = compressors; *comp != NULL; ++comp) {
	    if ((*comp)->compress_proto == dp[0]) {
		if (cp->state != NULL) {
		    (*cp->comp->decomp_free)(cp->state);
		    cp->state = NULL;
		}
		cp->comp = *comp;
		cp->state = (*comp)->decomp_alloc(dp, CCP_OPT_LENGTH(dp));
		cp->flags |= CCP_ISUP;
		if (cp->state != NULL
		    && (*cp->comp->decomp_init)
		        (cp->state, dp, clen, 0, 0, 8192, 1))
		    cp->flags = (cp->flags & ~CCP_ERR) | CCP_DECOMP_RUN;
		break;
	    }
	}
	break;

    case CCP_CONFNAK:
    case CCP_CONFREJ:
	cp->flags &= ~(CCP_DECOMP_RUN | CCP_ISUP);
	break;

    case CCP_RESETACK:
	if (cp->flags & CCP_ISUP) {
	    if (cp->state && (cp->flags & CCP_DECOMP_RUN)) {
		(*cp->comp->decomp_reset)(cp->state);
		cp->flags &= ~CCP_ERROR;
	    }
	}
	break;
    }
}

void
show_time(f, c)
    FILE *f;
    int c;
{
    time_t t;
    int n;
    struct tm *tm;

    if (c == RECMARK_TIMESTART) {
	t = getc(f);
	t = (t << 8) + getc(f);
	t = (t << 8) + getc(f);
	t = (t << 8) + getc(f);
	printf("start %s", ctime(&t));
	start_time = t;
	start_time_tenths = 0;
	tot_sent = tot_rcvd = 0;
    } else {
	n = getc(f);
	if (c == RECMARK_TIMEDELTA32) {
	    for (c = 3; c > 0; --c)
		n = (n << 8) + getc(f);
	}
	if (abs_times) {
	    n += start_time_tenths;
	    start_time += n / 10;
	    start_time_tenths = n % 10;
	    tm = localtime(&start_time);
	    printf("time  %.2d:%.2d:%.2d.%d", tm->tm_hour, tm->tm_min,
		   tm->tm_sec, start_time_tenths);
	    printf("  (sent %d, rcvd %d)\n", tot_sent, tot_rcvd);
	} else
	    printf("time  %.1fs\n", (double) n / 10);
    }
}
